//
//  User.m
//  Pennyworth
//
//  Created by Chris Karr on 12/24/07.
//  Copyright 2007 Chris J. Karr. All rights reserved.
//

#import <SSCrypto/SSCrypto.h>

#import "AppDelegate.h"
#import "AnalyticsLogger.h"
#import "User.h"
#import "C45Learner.h"
#import "Observation.h"
#import "TrainingManager.h"
#import "Context.h"
#import "LabelManager.h"
#import "PredictionManager.h"

#define SAVED_ACTIVITIES @"Saved Activities"
#define SAVED_LOCATIONS @"Saved Locations"
#define SAVED_SOCIALS @"Saved Socials"

#define ENCRYPTED_KEY @"Encrypted Key"
#define ENCRYPTED_PREDICTION @"Encrypted Prediction"

#define DISTRIBUTED_NOTIFICATIONS @"Use_Distributed_Notifications"

@implementation User

@synthesize location;
@synthesize activity;
@synthesize social;

@synthesize locations;
@synthesize activities;
@synthesize socials;

@synthesize learner;

- (User *) init
{
	if (self == [super init])
	{
		learners = [[NSMutableDictionary alloc] init];
		
		NSArray * keys = [NSArray arrayWithObjects:@"Activity", @"Location", @"Social Context", nil];
		
		[self willChangeValueForKey:@"learnerKeys"];
		
		for (NSString * key in keys)
		{
			learner = [[C45Learner alloc] init];
			[learner setKey:key];
			[learners setValue:learner forKey:key];
			[learner release];
		}
		
		[self didChangeValueForKey:@"learnerKeys"];
	}
	
	return self;
}

- (void) awakeFromNib
{
	cachedObservations = [[NSMutableDictionary alloc] init];

	[self.activities setSortDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"description" ascending:YES]]];
	[self.activities setAutomaticallyRearrangesObjects:YES];

	[self.locations setSortDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"description" ascending:YES]]];
	[self.locations setAutomaticallyRearrangesObjects:YES];
	
	[self.socials setSortDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"description" ascending:YES]]];
	[self.socials setAutomaticallyRearrangesObjects:YES];
	
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processPrediction:) name:PREDICTION_FETCHED object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(manageLabels:) name:LABEL_OPERATION object:nil];

	[[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(broadcast:) userInfo:nil repeats:NO] retain];
}

- (void) broadcast:(NSTimer *) theTimer
{
	NSMutableDictionary * dict = [NSMutableDictionary dictionary];
	
	[dict setValue:self.activities forKey:@"Activity"];
	[dict setValue:self.locations forKey:@"Location"];
	[dict setValue:self.socials forKey:@"Social Context"];
	
	for (NSString * key in [learners allKeys])
	{
		NSArrayController * controller = [dict valueForKey:key];
		
		if (controller != nil)
		{
			C45Learner * myLearner = [learners valueForKey:key];
			
			for (NSDictionary * example in myLearner.examples)
			{
				NSString * label = [example valueForKey:LABEL_KEY];
				
				if (![[controller arrangedObjects] containsObject:label])
					[controller addObject:label];
			}
			
			[controller rearrangeObjects];
		}
	}
	
	[[NSNotificationCenter defaultCenter] postNotificationName:LABELS_UPDATED object:self userInfo:dict];
}

- (void) manageLabels:(NSNotification *) note
{
	NSDictionary * op = [note userInfo];
	
	NSArrayController * controller = self.activities;
	
	if ([[op valueForKey:LABEL_TYPE] isEqual:@"Location"])
		controller = self.locations;
	else if ([[op valueForKey:LABEL_TYPE] isEqual:@"Social Context"])
		controller = self.socials;

	[controller removeObject:[op valueForKey:ORIGINAL_LABEL]];
	
	if ([[op valueForKey:OPERATION] isEqual:RENAME_OPERATION])
	{
		NSString * newLabel = [op valueForKey:NEW_LABEL];
		
		if ([[controller arrangedObjects] indexOfObject:newLabel] == NSNotFound)
			[controller addObject:newLabel];
	}

	[controller rearrangeObjects];

	[self broadcast:nil];
}

- (NSArray *) learnerKeys
{
	return [learners allKeys];
}

- (Learner *) learnerNamed:(NSString *) key
{
	return [learners valueForKey:key];
}

- (NSData *) encryptData:(NSData *) data withKey:(NSData *) key
{
	SSCrypto * crypto = [[SSCrypto alloc] initWithSymmetricKey:key];
	[crypto setClearTextWithData:data];
	
	NSData * encrypted = [crypto encrypt:@"aes256"];
	
	[crypto release];
	
	return encrypted;
}

- (void) processPrediction:(NSNotification *) note
{
	NSDictionary * userInfo = [note userInfo];
	
	NSString * key = [userInfo valueForKey:KEY];
	NSString * prediction = [userInfo valueForKey:PREDICTION];

	if (key == nil)
		return;

	NSArrayController * controller = nil;
	
	if ([key isEqualToString:@"Activity"])
	{
		controller = activities;
		self.activity = prediction;
	}
	else if ([key isEqualToString:@"Location"])
	{
		controller = locations;
		self.location = prediction;
	}
	else if ([key isEqualToString:@"Social Context"])
	{
		controller = socials;
		self.social = prediction;
	}
	
	NSNumber * distributed = [[NSUserDefaults standardUserDefaults] valueForKey:DISTRIBUTED_NOTIFICATIONS];

	if (distributed == nil || [distributed boolValue])
	{
		if (![[cachedObservations valueForKey:key] isEqualToString:prediction])
		{
			NSString * notePassword = [((AppDelegate *) [NSApp delegate]) notePassword];
			
			if (notePassword != nil && ![notePassword isEqualToString:@""])
			{
				NSMutableDictionary * encrypted = [NSMutableDictionary dictionaryWithDictionary:userInfo];
				[encrypted removeObjectForKey:KEY];
				[encrypted removeObjectForKey:PREDICTION];
				
				NSData * encKey = [self encryptData:[key dataUsingEncoding:NSUTF8StringEncoding] 
											withKey:[notePassword dataUsingEncoding:NSUTF8StringEncoding]];
				
				[encrypted setValue:encKey forKey:ENCRYPTED_KEY];

				NSData * encPrediction = [self encryptData:[prediction dataUsingEncoding:NSUTF8StringEncoding] 
												   withKey:[notePassword dataUsingEncoding:NSUTF8StringEncoding]];
				
				[encrypted setValue:encPrediction forKey:ENCRYPTED_PREDICTION];
				
				[[NSDistributedNotificationCenter defaultCenter] postNotificationName:PREDICTION_FETCHED 
																			   object:[[NSBundle mainBundle] bundleIdentifier]
																			 userInfo:encrypted];
			}
			else
			{
				[[NSDistributedNotificationCenter defaultCenter] postNotificationName:PREDICTION_FETCHED 
																			   object:[[NSBundle mainBundle] bundleIdentifier]
																			 userInfo:userInfo];
			}

			[cachedObservations setValue:prediction forKey:key];
		}
	}

	if (controller != nil && prediction != nil)
	{
		if ([[controller arrangedObjects] indexOfObject:prediction] == NSNotFound)
			[controller addObject:prediction];
	}
}

- (IBAction) removeLastExample:(id) sender
{
	if (![[NSUserDefaults standardUserDefaults] boolForKey:PREDICTIONS_ACTIVE])
		NSRunInformationalAlertPanel (@"Predictions Paused", @"Predictions are currently paused. Please resume predictions to allow the changes to take effect.", @"Ok", nil, nil);

	NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Undid Prediction Correction", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil];
	[[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject];

	[[NSNotificationCenter defaultCenter] postNotificationName:REMOVE_LAST_EXAMPLE object:self userInfo:nil];
}

- (IBAction) saveValues:(id) sender
{
	NSMutableDictionary * dict = [NSMutableDictionary dictionary];
	
	if (self.location != nil)
	{
		if ([[self.locations arrangedObjects] indexOfObject:self.location] == NSNotFound)
			[self.locations addObject:self.location];

		[dict setValue:self.location forKey:@"Location"];
	}

	if (self.activity != nil)
	{
		if ([[self.activities arrangedObjects] indexOfObject:self.activity] == NSNotFound)
			[self.activities addObject:self.activity];
		
		[dict setValue:self.activity forKey:@"Activity"];
	}
	
	if (self.social != nil)
	{
		if ([[self.socials arrangedObjects] indexOfObject:self.social] == NSNotFound)
			[self.socials addObject:self.social];
		
		[dict setValue:self.social forKey:@"Social Context"];
	}
	
	[dict setValue:[context observationArray] forKey:FEATURE_LIST];
	
	[[NSNotificationCenter defaultCenter] postNotificationName:ADD_EXAMPLE object:self userInfo:dict];
	
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	[defaults setValue:[self.activities arrangedObjects] forKey:SAVED_ACTIVITIES];
	[defaults setValue:[self.locations arrangedObjects] forKey:SAVED_LOCATIONS];
	[defaults setValue:[self.socials arrangedObjects] forKey:SAVED_SOCIALS];
	
	[[sender window] orderOut:self];

	[defaults setBool:NO forKey:IS_TRAINING];
	
	self.location = nil;
	self.social	= nil;
	self.activity = nil;

	[self broadcast:nil];
	
	NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Completed Prediction Correction", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil];
	[[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject];

}

- (IBAction) resetLearners:(id) sender
{
	if (NSOKButton == NSRunAlertPanel (@"Reset Learners?", @"Are you sure that you want to reset the learners? They will require retraining.", 
																@"Yes", @"No", nil))
	{
		for (Learner * l in [learners allValues])
			[l reset];
			
		self.activity = nil;
		self.location = nil;
		self.social = nil;
		
		NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Reset Learners", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil];
		[[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject];
	}
	else
	{
		NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Declined to Reset Learners", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil];
		[[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject];
	}
}

- (IBAction) resetLabels:(id) sender
{
	if (NSOKButton == NSRunAlertPanel (@"Reset Values?", @"Are you sure that you want to reset the values previously used for each label?", 
																@"Yes", @"No", nil))
	{
		NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Reset Labels", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil];
		[[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject];

		[self.activities removeObjects:[self.activities arrangedObjects]];
		[self.locations removeObjects:[self.locations arrangedObjects]];
		[self.socials removeObjects:[self.socials arrangedObjects]];
	}
	else
	{
		NSDictionary * postObject = [NSDictionary dictionaryWithObjectsAndKeys:@"Declined to Reset Labels", ANALYTIC_NAME, @"", ANALYTIC_VALUE, nil];
		[[NSNotificationCenter defaultCenter] postNotificationName:ANALYTIC_EVENT object:postObject];
	}
}

@end
